﻿using System;
using System.Text;
using SoftwareConsulting.BI11.NumericalMethods.Interfaces;
using SoftwareConsulting.BI11.NumericalMethods.DhbFunctionEvaluation;
using SoftwareConsulting.BI11.NumericalMethods.Iterations;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{    
    /// <summary>
    /// Класс абстрактной функции плоности вероятности.
    /// </summary>
    /// <remarks>Функция плотности показывает вероятность того, что непрерывная случайная переменная
    /// принимает значение в интервале [x, x+dx]. Норма определенаа для случая, когда функция
    /// наложена на множество экспериментальных точек или гистограммы</remarks>
    public abstract class ProbabilityDensityFunction : IParametrizedOneVariableFunction
    {
        private RandomGaussianGenerator _randomGenerator = null;//генератор случайных чисел

        /// <summary>
        /// Вычисляет значение, основываясь на функции плотности
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public abstract double GetValue(double x);

        /// <summary>
        /// Возвращает/устанавливает параметры распределения
        /// </summary>
        public abstract double[] Parameters { get; set; }

        /// <summary>
        /// Возвращает значение распределения и приближенный градиент распределения с учетом параметров
        /// </summary>
        /// <param name="x"></param>
        /// <returns>0й элемент - значение распределения, 1й и последующие - градиент распределения</returns>
        public double[] GetValueAndApproximatedGradient(double x)
        {
            double temp, delta;
            double[] localParams = this.Parameters;
            double[] result = new double[localParams.Length + 1];
            result[0] = this.GetValue(x);
            for (int i = 0; i < localParams.Length; i++)
            {
                temp = localParams[i];
                delta = Math.Abs(temp) > MathExtended.DefaultNumericalPrecision
                                ? 0.0001 * temp : 0.0001;
                localParams[i] += delta;
                this.Parameters = localParams;
                result[i + 1] = (this.GetValue(x) - result[0]) / delta;
                localParams[i] = temp;
            }
            this.Parameters = localParams;
            return result;
        }

        /// <summary>
        /// Возвращает/устанавливает среднее распределения
        /// </summary>
        public abstract double Average { get; set; }

        /// <summary>
        /// Возвращает значение функции плотности распределения
        /// </summary>
        /// <param name="x"></param>
        /// <returns>вероятность нахождения случайной переменной меньше или равной, чем X</returns>
        public abstract double GetDistributionValue(double x);
        
        /// <summary>
        /// Возвращает вероятность нахождения случайной переменной в отрезке [x1;x2]
        /// </summary>
        /// <param name="x1"></param>
        /// <param name="x2"></param>
        /// <returns></returns>        
        public virtual double GetDistributionValue(double x1, double x2)
        {
            return GetDistributionValue(x2) - GetDistributionValue(x1);
        }

        /// <summary>
        /// Возвращает генератор случайных чисел
        /// </summary>
        protected RandomGaussianGenerator RandomGenerator
        {
            get
            {
                if (_randomGenerator == null)
                    _randomGenerator = new RandomGaussianGenerator();
                return _randomGenerator;
            }
        }

        /// <summary>
        /// Возвращает значение, для которого функция распределения равна указанному значению X (с проверкой на корректность)
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public double GetInversedDistributionValue(double x)
        {
            if (x < 0 || x > 1)
                throw new ArgumentOutOfRangeException("x");
            return GetInversedValue(x);
        }

        /// <summary>
        /// Возвращает эксцесс распределения
        /// </summary>
        public virtual double Kurtosis
        {
            get 
            { 
                return double.NaN; 
            }
        }

        /// <summary>
        /// Возвращает название распределения
        /// </summary>
        public abstract String Name { get; }

        /// <summary>
        /// Возвращает значение, для которого функция распределения равна указанному значению X
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        protected virtual double GetInversedValue(double x)
        {
            InversedProbabilityDensityFunction distribution = new InversedProbabilityDensityFunction(this, x);
            NewtonZeroFinder zeroFinder = new NewtonZeroFinder(distribution, this, this.Average);
            zeroFinder.SetDesiredPrecision(MathExtended.DefaultNumericalPrecision);
            zeroFinder.Evaluate();
            return zeroFinder.BestZeroApproximation;
        }

        /// <summary>
        /// Возвращает случайное вещественное число в соответствие с распределением
        /// </summary>
        /// <returns></returns>
        public virtual double GetRandomValue()
        {
            return GetInversedValue(this.RandomGenerator.NextDouble());
        }

        /// <summary>
        /// Возвращает коэффициент уклона распределения
        /// </summary>
        public virtual double Skewness
        {
            get 
            { 
                return double.NaN; 
            }
        }
        
        /// <summary>
        /// Возвращает/устанавливает стандартное отклонение дисперсии распределения
        /// </summary>
        /// <remarks>в дочерних классах должно реализовываться либо это свойство, либо дисперсия</remarks>
        public virtual double StandardDeviation
        {
            get 
            { 
                return Math.Sqrt(Variance); 
            }
            set 
            {
                Assert.Fail(Assert.Id.Assert00279);
            }
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder(this.Name);
            char[] separator = { '(', ' ' };
            double[] parameters = this.Parameters;
            for (int i = 0; i < parameters.Length; i++)
            {
                sb.Append(separator);
                sb.Append(parameters[i]);
                if (i == 0)
                    separator[0] = ',';
            }
            sb.Append(')');
            return sb.ToString();
        }

        /// <summary>
        /// Возвращает значение распределения и градиент распределения с учетом параметров
        /// </summary>
        /// <param name="x"></param>
        /// <returns>0й элемент - значение распределения, 1й и последующие - градиент распределения</returns>
        public virtual double[] GetValueAndGradient(double x)
        {
            return GetValueAndApproximatedGradient(x);
        }

        /// <summary>
        /// Возвращает дисперсию распределения
        /// </summary>
        public virtual double Variance
        {
            get
            {
                double v = this.StandardDeviation;
                return v * v;
            }
        }
    }
}
